package com.bsj.media.media.gb28181;

import bsj.com.faac.def.HexStr;
import cn.hutool.core.util.StrUtil;
import com.bsj.power.common.def.protocol.gb28181.RtpData;
import com.bsj.power.common.def.protocol.rtp.RtpParameterSet;
import com.bsj.power.common.util.Conv;
import com.bsj.power.common.util.ExceptionUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @author yinhao
 * @version 1.0
 * @description 用于存储GB28181数据流
 * @date 2024/6/19
 */
@Data
@Slf4j
public class Gb28181Rtp {
    /**
     * 终端号
     */
    private String terminalNo;
    private int vehicleId;
    /**
     * 通道号
     */
    private int chn;
    /**
     * key: terminalNo_chn
     */
    private String key;
    /**
     * udp传输：存储rtp的序列号
     */
    ConcurrentLinkedQueue<Integer> mSeqMap = null;
    /**
     * udp传输：存储每个rtp序列号的rtp包
     */
    private Map<Integer, byte[]> mPacketMap = null;
    /**
     * 存放不完整ps流
     */
    private ByteBuf incompletePs = Unpooled.buffer(100 * 1024);
    /**
     * 前包时间戳，用于判断是否结束
     */
    private long previousTimestamp = 0;
    /**
     * 视频流类型 0:mpeg  1:h264 2:mpjp 3:h265
     */
    private int videoStreamType = 0xff;
    /**
     * 音频流类型 0:ADPCM, 1:G711A 2:G726 3:AAC 4:G711U
     */
    private int audioStreamType = 0xff;
    /**
     * 当前流媒体类型
     * 0:视频I帧  1:视频P帧 2:视频B帧(暂未使用到) 3:音频
     */
    private byte dataType = (byte) 0xff;

    /**
     * 视频、或者音频的一帧数据
     */
    private ByteBuf oneFrame = Unpooled.buffer(100 * 1024);
    /**
     * 第一帧是否为I帧
     * 不为I帧，直接丢弃
     */
    private boolean mIsFirstI;
    /**
     * 分辨率
     */
    public byte oldRatio = 0;
    /**
     * 帧率
     */
    public byte oldRate = 0;
    /**
     * 存储sps等帧，但是没有关键帧
     */
    public byte[] spsCacheNotIDR;
    /**
     * 接收流时间，用于过期使用
     */
    public static long acceptStreamTime = System.currentTimeMillis();
    /**
     * 过期时间
     */
    static int expireTimeOne = 1 * 60 * 1000;

    public Gb28181Rtp(String terminalNo, int chn) {
        this.terminalNo = terminalNo;
        this.chn = chn;
        this.key = String.format("%s_%d", terminalNo, chn);
    }

    /**
     * 解析每包的rtp数据
     *
     * @param rtpData: UDP 由于在外网环境下(特别是4G)，乱序 丢包情况较为严重。所以处理方式和TCP有所区别，
     * @return
     **/
    public List<RtpData> analysisData(byte[] rtpData) {
        acceptStreamTime = System.currentTimeMillis();
        if (!checkRtpHeader(rtpData)) {
            return null;
        }
        List<RtpData> rtpDatas = new ArrayList<>();
        List<byte[]> handlerRtpDatas = udpSeqHandler(rtpData);
        if (Objects.isNull(handlerRtpDatas) || handlerRtpDatas.size() <= 0) {
            return null;
        }
        for (byte[] handlerRtpData : handlerRtpDatas) {
            try {
                // 根据当次包的时间戳和数据类型，判断上个包是否已经是完整包
                // rtp协议规定，该值必须使用90 kHz 时钟频率。时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动，并进行同步控制。
                long currentTimestamp = Conv.getIntNetOrder(handlerRtpData, 4); // 当前包时间戳
                if (currentTimestamp != previousTimestamp || incompletePs.getInt(0) == 0x1C0 || Conv.getIntNetOrder(handlerRtpData, 12) == 0x1C0) {// 就算时间戳相同的音频，现在都当做不同包组fd
                    // 上一流完整处理
                    byte[] handlerPs = ByteBufUtil.getBytes(incompletePs); // 上一流完整包
                    incompletePs.clear();
                    incompletePs.writeBytes(handlerRtpData, 12, handlerRtpData.length - 12);
                    previousTimestamp = currentTimestamp;
                    RtpData rtpHandlerAfter = handlerPs(handlerPs, (long) ((double) currentTimestamp / 90000 * 1000));
                    if (Objects.nonNull(rtpHandlerAfter)) {
                        rtpDatas.add(rtpHandlerAfter);
                    }
                } else {
                    incompletePs.writeBytes(handlerRtpData, 12, handlerRtpData.length - 12);
                }
            } catch (Exception e) {
                log.error("analysisData error : {}", ExceptionUtil.getStackStr(e));
            }
        }
        return rtpDatas;
    }

    /**
     * @param timeStamp: rtp中的时间戳
     * @return
     **/
    private RtpData handlerPs(byte[] psData, long timeStamp) {
        // 清除上条残留信息
        dataType = (byte) 0xff;
        oneFrame.clear();
        if (Objects.isNull(psData) || psData.length <= 0) {
            return null;
        }

        try {
            int pesHeaderOffset = analysisPSHAndSYSAndPsm(psData);
            // 解析pes头部和流媒体信息 知道音频和视频编码格式才去解析pes
            if (pesHeaderOffset >= 0 && pesHeaderOffset < psData.length
                    && (audioStreamType != 0xff || videoStreamType != 0xff)
                    && analysisPes(psData, pesHeaderOffset)) {
                return handlerMedia(timeStamp);
            }
        } catch (Exception e) {
            log.error("处理ps流数据异常清空缓存区:" + ExceptionUtil.getStackStr(e));
            incompletePs.clear();
        }

        return null;
    }

    /**
     * 处理流媒体数据
     *
     * @return
     **/
    private RtpData handlerMedia(long timeStamp) {
        if (!oneFrame.isReadable()) {
            return null;
        }
        RtpData rtpData = null;
        try {
            rtpData = new RtpData(StrUtil.format("{}_{}", terminalNo, chn), chn, dataType, terminalNo);
            byte[] mediaData = null;
            if (dataType == 0 || dataType == 1 || dataType == 2) {
                mediaData = handlerVideo();
                rtpData.setPayLoad(videoStreamType);
            } else if (dataType == 3) {
                mediaData = handlerAudio();
                rtpData.setPayLoad(audioStreamType);
            }
            if (Objects.isNull(mediaData) || mediaData.length <= 0) {
                return null;
            }

            // 组FD
//            byte[] fdData = packageFdData(mediaData, timeStamp);
            byte[] fdData = mediaData;

            // 补充RtpData数据
            rtpData.setData(fdData);
            rtpData.setRateType(oldRate);
            rtpData.setResolutionRatio(oldRatio);
            rtpData.setTime(timeStamp);
        } catch (Exception e) {
            log.warn("打包RtpData数据异常:" + ExceptionUtil.getStackStr(e));
            return null;
        }
        return rtpData;
    }

    private byte[] packageFdData(byte[] mediaData, long timeStamp) {
        byte[] data = new byte[2 + 2 + 1 + 10 + 4 + 21 + mediaData.length + 1 + 1];
        int offset = 0;
        Conv.setShortNetOrder(data, offset, 0xFDFD);
        offset += 2;
        Conv.setShortNetOrder(data, offset, 0x0F00);
        offset += 2;
        data[offset++] = 10;// 终端号实际长度
        // 终端号
        System.arraycopy(HexStr.toArray(terminalNo), 0, data, offset, 10);
        offset += 10;
        // 消息体长度
        Conv.setIntNetOrder(data, offset, 21 + mediaData.length);
        offset += 4;
        data[offset++] = (byte) chn; // 通道号
        data[offset++] = dataType;
        data[offset++] = (byte) (dataType == 3 ? audioStreamType : videoStreamType); // 编码格式
        if (dataType == 3) {
            data[offset++] = 0; // 音频位率
            data[offset++] = 0; // 采样率
        } else {
            data[offset++] = oldRatio; // 分辨率
            data[offset++] = oldRate; // 帧率
        }
        Conv.setLongNetOrder(data, offset, timeStamp); // 时间戳
        offset += 8;
        offset += 8; // 多媒体id
        System.arraycopy(mediaData, 0, data, offset, mediaData.length); // 数据体
        offset += mediaData.length;
        offset++; // 检验码
        data[offset++] = 0x0D; // 结束符
        return data;
    }

    /**
     * 判断是否过期
     *
     * @param currentTime:
     * @return
     **/
    public boolean isExpire(long currentTime) {
        return currentTime - acceptStreamTime > expireTimeOne;
    }

    /**
     * 获取打包fd的音频数据
     *
     * @return
     **/
    private byte[] handlerAudio() {
        return ByteBufUtil.getBytes(oneFrame);
    }

    /**
     * 获取打包fd的视频数据，需要获取帧率、分辨率、和有些sps跟I帧分开传输
     *
     * @return
     **/
    private byte[] handlerVideo() {
        byte[] videoData = ByteBufUtil.getBytes(oneFrame);
        if (dataType == 0) {
            // I帧
            byte[] sps = decodeSPS.ReadSPSNaluFromBuf(videoData, videoData.length, 0, videoStreamType == 3 ? RtpParameterSet.rtpCodeTypeH265 : RtpParameterSet.rtpCodeTypeH264);
            if (sps != null && sps.length > 0) {
                int[] nLen = new int[1];
                nLen[0] = sps.length;
                int[] width = new int[1];
                int[] height = new int[1];
                int[] fps = new int[1];
                if (videoStreamType == 3) {
                    decodeSPS.h265_decode_sps(sps, nLen, width, height, fps);
                } else {
                    decodeSPS.h264_decode_sps(sps, nLen, width, height, fps);
                }
                if (width[0] != 0 && height[0] != 0) {
                    int ratio = RatioUtil.getRatio(width[0], height[0]);
                    if (oldRatio != (byte) ratio) {
                        log.info(StrUtil.format("分辨率: {}:{}, key={}", width[0], height[0], key));
                    }
                    oldRatio = (byte) ratio;
                }
                if (oldRate == 0 || (fps[0] != 0 && fps[0] != oldRate)) {

//                    log.info(StrUtil.format("读取帧率:{}, key={}", fps[0], key));
                }
                if (fps[0] > 0) {
                    int i = fps[0] / 2;
                    oldRate = (byte) i;
//                    oldRate = oldRatio / 2;
                } else {
                    oldRate = 15; // 默认15
                }
                if (!decodeSPS.JudgeHasKeyFrameNaluFromBuf(videoData, videoData.length, 0, videoStreamType == 3 ? RtpParameterSet.rtpCodeTypeH265 : RtpParameterSet.rtpCodeTypeH264)) {
                    // 无关键帧
                    spsCacheNotIDR = ByteBufUtil.getBytes(oneFrame);
                    log.warn(StrUtil.format("{}, 接收到SPS帧,未有关键帧的数据", key));
                    return null;
                } else {
                    // 有关键帧，设置帧
                    return videoData;
                }
            } else {
                // 无sps时，查看是否有缓存sps帧
                if (Objects.nonNull(spsCacheNotIDR) && spsCacheNotIDR.length > 0) {
                    return ByteBufUtil.getBytes(Unpooled.wrappedBuffer(spsCacheNotIDR, videoData));
                } else {
                    log.warn(StrUtil.format("{} 接收到错误I帧,找不到sps帧", key));
                    return null;
                }
            }
        } else {
            // 非关键帧
            return videoData;
        }
    }

    /**
     * 解析pes数据, 注意一个ps包，有多个pes包,多个pes就是I帧
     *
     * @param psDataByte:
     * @param offset:
     * @return
     **/
    public boolean analysisPes(byte[] psDataByte, int offset) {
        int tempOffset = offset;
        while (tempOffset < psDataByte.length) {
            // 解析多pes包
            int packet_start_code_prefix = Conv.getIntNetOrder_3(psDataByte, tempOffset);
            tempOffset += 3;
            byte stream_id = psDataByte[tempOffset++];
            int PES_packet_length = Conv.getShortNetOrder(psDataByte, tempOffset); // 内容长度+流媒体数据
            tempOffset += 2;
            if (tempOffset + PES_packet_length > psDataByte.length) {
                // 这是异常情况
//                LoggerPrintUtil.printLoggerByTerminalNo(logger, StrUtil.format("key={},PES异常数据: tempOffset={}, PES_packet_length={}, psDataByte.length={}, offset={}", key, tempOffset, PES_packet_length, psDataByte.length, offset), Level.INFO, terminalNo);
                return false;
            }
            // 暂时只解析 packet_start_code_prefix == 1的内容
            if (packet_start_code_prefix == 1) {
                tempOffset += 2; // '10' 到PES_extension_flag
                int PES_header_data_length = psDataByte[tempOffset++];
                tempOffset += PES_header_data_length; // 来到媒体流数据的起始指针
                int notStreamInfoLen = 2 + 1 + PES_header_data_length; // 不是流媒体数据的长度
                oneFrame.writeBytes(psDataByte, tempOffset, PES_packet_length - notStreamInfoLen);
                tempOffset += (PES_packet_length - notStreamInfoLen); // PES_packet_length - notStreamInfoLen是流媒体长度
            } else {
                tempOffset += PES_packet_length;
            }
        }


        return true;
    }

    /**
     * 解析psh，顺便判断是否有sys和psm,并一起解析
     *
     * @param psData: ps流数据
     * @return 返回pes头部的偏移量
     **/
    private int analysisPSHAndSYSAndPsm(byte[] psData) {
        int pesOffset = -1;
        int offset = 0;
        // ps头
        if (psData.length >= 4 && Conv.getIntNetOrder(psData, offset) == 0x01ba) {
            offset += 4;
            int pack_stuffing_length = psData[offset + 9] & 0x07;
            offset = offset + pack_stuffing_length + 10; // sys的偏移量或者pes的偏移量
            // 判断是否系统头
            if (Conv.getIntNetOrder(psData, offset) == 0x01bb) {
                // 解析sys
                offset = analysisSYS(psData, offset);
                // 有sys,就需要有psm
                if (offset > 0 && offset + 4 < psData.length && Conv.getIntNetOrder(psData, offset) == 0x01bc) {
                    offset = analysisPsm(psData, offset);
                    if (offset > 0 && offset < psData.length) {
                        pesOffset = offset;
                        mIsFirstI = true;
                        dataType = 0; // I帧
                    }
                }
            } else if (Conv.getIntNetOrder(psData, offset) == 0x01bc) {
                //也有ba过后是bc
                offset = analysisPsm(psData, offset);
                if (offset > 0 && offset < psData.length) {
                    pesOffset = offset;
                    mIsFirstI = true; // 第一次接受I帧
                    dataType = 0; // I帧
                }
            } else {
                if (!mIsFirstI) {
                    log.warn(StrUtil.format("{} 暂未收到关键帧信息!!!", key));
                } else {
                    pesOffset = offset;
                    dataType = 1; // p帧
                }
            }
        } else if (psData.length >= 4 && Conv.getIntNetOrder(psData, offset) == 0x01C0) {
            // 音频
            dataType = 3;
            pesOffset = offset;
        }
        return pesOffset;
    }

    /**
     * 解析psm内容，需要获取视频和音频的类型
     *
     * @param psData:
     * @param offset:
     * @return
     **/
    private int analysisPsm(byte[] psData, int offset) {
        offset += (3 + 1); //  packet_start_code_prefix + map_stream_id
        int mapLength = Conv.getShortNetOrder(psData, offset); // program_stream_map_length
        offset += 2;
        int result = offset + mapLength;
        offset = offset + 2;
        int program_stream_info_length = Conv.getShortNetOrder(psData, offset);
        offset = offset + 2 + program_stream_info_length;
        int elementary_stream_map_length = Conv.getShortNetOrder(psData, offset);
        offset += 2;
        // 读取stream_type
        int tempOffset = offset;
        while (tempOffset < (offset + elementary_stream_map_length)) {
            byte stream_type = psData[tempOffset++]; // 流类型字段
            byte elementary_stream_id = psData[tempOffset++]; // 基本流标识字段 elementary_stream_id
            handlerStream(stream_type, elementary_stream_id);
            int elementary_stream_info_length = Conv.getShortNetOrder(psData, tempOffset);
            tempOffset += 2;
            tempOffset += elementary_stream_info_length;
        }
        // CRC_32
        tempOffset += 4;
        return result;
    }

    /**
     * 处理流媒体类型
     *
     * @param streamType: 流类型字段
     * @param streamId:   基本流标识字段
     * @return
     **/
    private void handlerStream(byte streamType, byte streamId) {
        switch (streamType) {
            case 0x1B:
                if (videoStreamType == 0xff) {
                    log.info(StrUtil.format("{}, 视频编码格式:{}", key, "h264"));
                }
                videoStreamType = 1;
                break;
            case 0x24:
                if (videoStreamType == 0xff) {
                    log.info(StrUtil.format("{}, 视频编码格式:{}", key, "h265"));
                }
                videoStreamType = 3;
                break;
            case (byte) 0x90:
                if (audioStreamType == 0xff) {
                    log.info(StrUtil.format("{}, 音频编码格式:{}", key, "G711A"));
                }
                audioStreamType = 1;
                break;
            case (byte) 0x91:
                if (audioStreamType == 0xff) {
                    log.info(StrUtil.format("{}, 音频编码格式:{}", key, "G711U"));
                }
                audioStreamType = 4;
                break;
            case 0x0f:
                if (audioStreamType == 0xff) {
                    log.info(StrUtil.format("{},音频编码格式:{}", key, "ACC"));
                }
                audioStreamType = 3;
                break;
            default:
                log.warn("该" + (streamId == (byte) 0xC0 ? "音频" : "视频") + "类型streamType=0x" + Integer.toHexString(Byte.toUnsignedInt(streamType)) + "不接收， terminalNo:" + terminalNo);
                break;
        }
    }

    /**
     * 解析sys，主要是返回下一个psm的偏移量，sys的内容暂时未用到
     *
     * @param psData:
     * @param offset:
     * @return
     **/
    private int analysisSYS(byte[] psData, int offset) {
        offset += 4; // system_header_start_code
        int headerLen = Conv.getShortNetOrder(psData, offset); //header_length
        offset += 2;
        offset += headerLen;
        return offset;
    }

    private List<byte[]> udpSeqHandler(byte[] rtpData) {
        if (Objects.isNull(mSeqMap)) {
            mSeqMap = new ConcurrentLinkedQueue<>();
        }
        if (Objects.isNull(mPacketMap)) {
            mPacketMap = new HashMap<>(20);
        }
        List<byte[]> handlerRtps = new ArrayList<>(15);
        int seq = Conv.getShortNetOrder(rtpData, 2); // 序列号
        mSeqMap.add(seq);
        mPacketMap.put(seq, rtpData);
        if (mSeqMap.size() >= 15) {
            // [4,7,8], poll取出4，mSeqMap.peek()-1得到的值是6，然后从4到6序列号取元素
            int firstSeq = mSeqMap.poll();
            int endSeq = mSeqMap.peek() - 1;
            //    logger.info(StrUtil.format("firstSeq:{}, endSeq:{}, mSeqMap:{}", firstSeq, endSeq, mSeqMap.toArray()));
            for (int i = firstSeq; i <= endSeq; i++) {
                byte[] tempRtp = mPacketMap.remove(i); // 要是每个序列号都有，就会有值，要是有丢包，就会是null值
                mSeqMap.remove(i); // 在队里中删除指定的序列号元素
                if (Objects.isNull(tempRtp)) {
                    log.debug(StrUtil.format("丢包或者乱序程度，导致获取不到该序列号:{}, key:{}", i, key));
                    continue;
                } else {
                    handlerRtps.add(tempRtp);
                }
            }
        }
        return handlerRtps;
    }

    /**
     * 检测rtp头是否正确
     *
     * @param rtpData:
     * @return
     **/
    private boolean checkRtpHeader(byte[] rtpData) {
        // 1、检测是否符合RTP头且是否为ps流，暂时不接收音频裸数据
        if (rtpData.length <= 12 || (rtpData[0] & 0xff) != 0x80 || (rtpData[1] & 0x7f) != 96) {
            log.warn("不符合rtp要求, copyData[0]:" + rtpData[0] + ", copyData[1]:" + rtpData[1]);
            return false;
        }
        return true;
    }
}
